package com.seed.util;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.mybatisflex.annotation.*;
import com.seed.core.action.Action;
import com.seed.core.annotation.*;
import com.seed.core.annotation.Stat;
import com.seed.core.enumeration.ColumnType;
import com.seed.core.enumeration.YesNo;
import com.seed.core.listener.OnSaveListener;
import com.seed.core.listener.OnSetListener;
import com.seed.core.listener.OnUpdateListener;
import com.seed.core.pojo.seed.*;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

public class ToSeedKit {

    public static Map<String, Seed> toSeeds(List<Class<?>> controllerList, Class<?>... otherEntityClasses) {
        Map<Class<?>, Seed> seeds = new HashMap<>();
        for (Class<?> controllerClass: controllerList) {
            Class<?> entityClass = getEntityClass(controllerClass);
            if(entityClass == null) continue;

            Seed seed = seeds.get(entityClass);
            if(seed == null) {
                seed = entityClassToSeed(entityClass);
                seeds.put(entityClass, seed);
            }

            initControllerClass(seed, controllerClass);
            initRelationSeeds(seeds, entityClass);
        }

        if(otherEntityClasses != null) {
            for (Class<?> entityClass: otherEntityClasses) {
                Seed seed = seeds.get(entityClass);
                if(seed == null) {
                    seed = entityClassToSeed(entityClass);
                    seeds.put(entityClass, seed);
                    initRelationSeeds(seeds, entityClass);
                }
            }
        }

        initRelations(seeds);

        Map<String, Seed> ret = new ConcurrentHashMap<>();
        for(Map.Entry<Class<?>, Seed> entry: seeds.entrySet()) {
            Seed seed = entry.getValue();
            ret.put(seed.getName(), seed);
        }

        return ret;
    }

    public static Map<String, Action> toActions(Class<?>... controllers) {
        Map<String, Action> ret = new HashMap<>();
        if(controllers == null) return ret;

        for (Class<?> controllerClass: controllers) {
            ret.putAll(initActions(controllerClass));
        }

        return ret;
    }

//    private static List<Class<?>> getControllerClassList(Class<?> baseClass) {
//        List<Class<?>> classList = ReflectKit.getClassList(baseClass);
//        List<Class<?>> ret = new ArrayList<>();
//        for (Class<?> clazz: classList) {
//            RestController controller = clazz.getAnnotation(RestController.class);
//            if(controller != null) ret.add(clazz);
//        }
//
//        return ret;
//    }
//
//    private static List<Class<?>> getEntityClassList(Class<?> baseClass) {
//        List<Class<?>> classList = ReflectKit.getClassList(baseClass);
//        List<Class<?>> ret = new ArrayList<>();
//        for (Class<?> clazz: classList) {
//            Table table = clazz.getAnnotation(Table.class);
//            if(table != null) ret.add(clazz);
//        }
//
//        return ret;
//    }

    private static Seed entityClassToSeed(Class<?> entityClass) {
        Seed seed = new Seed();
        initColumns(seed, entityClass);
        seed.setName(StrUtil.lowerFirst(entityClass.getSimpleName()));

        Table table = entityClass.getAnnotation(Table.class);
        if(table == null) {
            throw new RuntimeException(String.format("请为【%s】加上【@Table】注解", entityClass.getName()));
        }

        seed.setTableName(table.value());
        seed.setTableSchema(table.schema());

        Schema schema = entityClass.getAnnotation(Schema.class);
        if(schema == null) {
//            seed.setLabel(table.comment());
//            seed.setComment(table.comment());
        } else {
//            String label = table.comment();
//            if(StrUtil.isBlank(label)) label = schema.description();
//            seed.setLabel(label);
//            seed.setComment(schema.description());
        }

//        IdColumn idColumn = entityClass.getAnnotation(IdColumn.class);
//        if(idColumn != null) {
//            checkColumnExist(seed, idColumn.value());
//            seed.setIdColumn(idColumn.value());
//        }
        NameColumn nameColumn = entityClass.getAnnotation(NameColumn.class);
        if(nameColumn != null) {
            checkColumnExist(seed, nameColumn.value());
            seed.setNameColumn(nameColumn.value());
        }

        OnSet onSet = entityClass.getAnnotation(OnSet.class);
        if(onSet != null) {
            seed.setOnSet(Stream.of(onSet.value())
                    .map(clazz -> (OnSetListener) ReflectUtil.newInstance(clazz))
                    .toList()
            );
        }
        OnSave onSave = entityClass.getAnnotation(OnSave.class);
        if(onSave != null) {
            seed.setOnSave(Stream.of(onSave.value())
                    .map(clazz -> (OnSaveListener) ReflectUtil.newInstance(clazz))
                    .toList()
            );
        }
        OnUpdate onUpdate = entityClass.getAnnotation(OnUpdate.class);
        if(onUpdate != null) {
            seed.setOnUpdate(Stream.of(onUpdate.value())
                    .map(clazz -> (OnUpdateListener) ReflectUtil.newInstance(clazz))
                    .toList()
            );
        }

        return seed;
    }

    private static void initControllerClass(Seed seed, Class<?> controllerClass) {
        Declares declares = controllerClass.getAnnotation(Declares.class);
        if(declares != null && declares.value().length > 0) {
            seed.setDeclares(StrKit.toList(declares.value()));
        }

        Searches searches = controllerClass.getAnnotation(Searches.class);
        if(searches != null && searches.value().length > 0) {
            seed.setSearches(StrKit.toList(searches.value()));
        }

        Orders orders = controllerClass.getAnnotation(Orders.class);
        if(orders != null && orders.value().length > 0) {
            seed.setOrders(StrKit.toList(orders.value()));
        }

        Stat[] statList = controllerClass.getAnnotationsByType(Stat.class);
        if(statList.length > 0) {
            Map<String, com.seed.core.pojo.seed.Stat> statMap = new HashMap<>();
            for(Stat stats: statList) {
                com.seed.core.pojo.seed.Stat stat = new com.seed.core.pojo.seed.Stat();
//                stat.setLabel(stats.label());
//                stat.setComment(stats.comment());
                stat.setType(stats.type());
                stat.setDeclare(stats.declare());
                stat.setSearches(stats.searches());
                statMap.put(stats.name(), stat);
            }
            seed.setStats(statMap);
        }

//        UseKeyword useKeyword = controllerClass.getAnnotation(UseKeyword.class);
//        if(useKeyword != null) {
//            seed.setUseKeyword(useKeyword.value());
//        }
//
//        CheckData checkData = controllerClass.getAnnotation(CheckData.class);
//        if(checkData != null) {
//            seed.setCheckData(checkData.value());
//        }
//
//        CheckField checkField = controllerClass.getAnnotation(CheckField.class);
//        if(checkField != null) {
//            seed.setCheckField(checkField.value());
//        }

        seed.setActions(initActions(controllerClass));
    }

    private static Map<String, Action> initActions(Class<?> controllerClass) {
        Map<String, Action> ret = new HashMap<>();

        for(Method method: ReflectUtil.getMethods(controllerClass)) {
            Set<RequestMethod> methods = new HashSet<>();
//            RequestMapping request = method.getAnnotation(RequestMapping.class);
//            if(request != null) methods.addAll(List.of(request.method()));
//
//            GetMapping get = method.getAnnotation(GetMapping.class);
//            if(get != null) methods.add(RequestMethod.GET);
//
//            PostMapping post = method.getAnnotation(PostMapping.class);
//            if(post != null) methods.add(RequestMethod.POST);
//
//            PutMapping put = method.getAnnotation(PutMapping.class);
//            if(put != null) methods.add(RequestMethod.PUT);
//
//            DeleteMapping delete = method.getAnnotation(DeleteMapping.class);
//            if(delete != null) methods.add(RequestMethod.DELETE);
//
//            if(methods.isEmpty()) continue;
//
//            String name = method.getName();
//            Action action = new Action();
//            action.setName(name);
//            initAction(action, method);
//
//            Action oldAction = ret.get(name);
//            if(oldAction == null) {
//                action.setMethods(new ArrayList<>(methods));
//                ret.put(name, action);
//            } else {
//                if(methods.contains(RequestMethod.PUT)) {
//                    oldAction.getMethods().addAll(methods);
//                } else {
//                    methods.addAll(oldAction.getMethods());
//                    action.setMethods(new ArrayList<>(methods));
//                    ret.put(name, action);
//                }
//            }
        }

        return ret;
    }

    private static void initAction(Action action, Method method) {
//        Declares declares = method.getAnnotation(Declares.class);
//        if(declares != null && declares.value().length > 0) {
//            action.setDeclares(StrKit.toList(declares.value()));
//        }
//
//        Searches searches = method.getAnnotation(Searches.class);
//        if(searches != null && searches.value().length > 0) {
//            action.setSearches(StrKit.toList(searches.value()));
//        }
//
//        Orders orders = method.getAnnotation(Orders.class);
//        if(orders != null && orders.value().length > 0) {
//            action.setOrders(StrKit.toList(orders.value()));
//        }
//
//        Stat[] statList = method.getAnnotationsByType(Stat.class);
//        if(statList.length > 0) {
//            Map<String, com.seed.core.pojo.seed.Stat> statMap = new HashMap<>();
//            for(Stat seedStat: statList) {
//                com.seed.core.pojo.seed.Stat stat = new com.seed.core.pojo.seed.Stat();
//                stat.setType(seedStat.type());
//                stat.setDeclare(seedStat.declare());
//                stat.setSearches(seedStat.searches());
//                statMap.put(seedStat.name(), stat);
//            }
//            action.setStats(statMap);
//        }
//
//        SeedDeclare seedDeclare = method.getAnnotation(SeedDeclare.class);
//        if(seedDeclare != null) {
//            String declare1 = seedDeclare.declare();
//            if(StrUtil.isNotBlank(declare1)) action.setDeclare(declare1);
//            String value = seedDeclare.value();
//            if(StrUtil.isNotBlank(value)) action.setDeclareValue(value);
//        }
//
//        SeedName seedName = method.getAnnotation(SeedName.class);
//        if(seedName != null) {
//            action.setSeedName(seedName.value());
//        }
    }

    private static void initRelationSeeds(Map<Class<?>, Seed> seeds, Class<?> entityClass) {
        for(Field field: ReflectUtil.getFields(entityClass)) {
            HasOne hasOne = field.getAnnotation(HasOne.class);
            BelongsTo belongsTo = field.getAnnotation(BelongsTo.class);
            HasMany hasMany = field.getAnnotation(HasMany.class);
            Habtm habtm = field.getAnnotation(Habtm.class);
            if(hasOne == null && belongsTo == null && hasMany == null && habtm == null) continue;

            Class<?> targetEntityClass = getFieldType(field);
            Seed targetSeed = seeds.get(targetEntityClass);
            if(targetSeed == null) {
                targetSeed = entityClassToSeed(targetEntityClass);
                seeds.put(targetEntityClass, targetSeed);
            }

            if(habtm != null) {
                Class<?> throughClass = habtm.through();
                Seed throughSeed = seeds.get(throughClass);
                if(throughSeed == null) {
                    throughSeed = entityClassToSeed(throughClass);
                    seeds.put(throughClass, throughSeed);
                }
            }
        }
    }

    private static void initColumns(Seed seed, Class<?> entityClass) {
//        StringBuilder primaryKeys = new StringBuilder();
        LinkedHashMap<String, Column> columns = new LinkedHashMap<>();
        for(Field field: ReflectUtil.getFields(entityClass)) {
//            Id id = field.getAnnotation(Id.class);
//            String fieldName = field.getName();
//            String columnOrCross = StrUtil.toUnderlineCase(fieldName);
//            if(id != null) {
//                if(!primaryKeys.isEmpty()) primaryKeys.append(',');
//                primaryKeys.append(columnOrCross);
//            }

//            Column column = getColumn(field);
//            if(column != null) columns.put(column.getName(), column);
        }

//        seed.setColumns(columns);
    }

    private static void initRelations(Map<Class<?>, Seed> seeds) {
        for(Map.Entry<Class<?>, Seed> entry: seeds.entrySet()) {
            Class<?> entityClass = entry.getKey();
            Seed seed = entry.getValue();
            LinkedHashMap<String, Relation> relations = new LinkedHashMap<>();
            for(Field field: ReflectUtil.getFields(entityClass)) {
                String fieldName = field.getName();
                String columnOrCross = StrUtil.toUnderlineCase(fieldName);
                Class<?> targetEntityClass = getFieldType(field);
                HasOne hasOne = field.getAnnotation(HasOne.class);
                if(hasOne != null) {
                    relations.put(columnOrCross, new HasOnes().of(seeds, entityClass, targetEntityClass, hasOne));
                    continue;
                }

                BelongsTo belongsTo = field.getAnnotation(BelongsTo.class);
                if(belongsTo != null) {
                    relations.put(columnOrCross, new BelongsTos().of(seeds, entityClass, targetEntityClass, belongsTo));
                    continue;
                }

                HasMany hasMany = field.getAnnotation(HasMany.class);
                if(hasMany != null) {
                    relations.put(columnOrCross, new HasManys().of2(seeds, entityClass, targetEntityClass, hasMany));
                    continue;
                }

                Habtm habtm = field.getAnnotation(Habtm.class);
                if(habtm != null) {
                    relations.put(columnOrCross, new Habtms().of2(seeds, entityClass, targetEntityClass, habtm));
                }
            }
            seed.setRelations(relations);
        }
    }

    private static Column getColumn(Field field) {
        RelationOneToOne oneToOne = field.getAnnotation(RelationOneToOne.class);
        RelationOneToMany oneToMany = field.getAnnotation(RelationOneToMany.class);
        RelationManyToOne manyToOne = field.getAnnotation(RelationManyToOne.class);
        RelationManyToMany manyToMany = field.getAnnotation(RelationManyToMany.class);
        HasOne hasOne = field.getAnnotation(HasOne.class);
        BelongsTo belongsTo = field.getAnnotation(BelongsTo.class);
        HasMany hasMany = field.getAnnotation(HasMany.class);
        Habtm habtm = field.getAnnotation(Habtm.class);
        if(oneToOne != null || oneToMany != null || manyToOne != null || manyToMany != null
                ||hasOne != null || belongsTo != null || hasMany != null || habtm != null) return null;

        com.mybatisflex.annotation.Column column = field.getAnnotation(com.mybatisflex.annotation.Column.class);
        if(column != null && column.ignore()) return null;

        Column ret = null; //new Column();
        String fieldName = field.getName();
        Id id = field.getAnnotation(Id.class);
        if(id != null) {
//            ret.setName(StrUtil.toUnderlineCase(fieldName));
//            ret.setField(fieldName);
//            ret.setType(ColumnType.CHAR);
//            ret.setLength(17);
        }

//        if(column == null) {
//            ret.setName(StrUtil.toUnderlineCase(fieldName));
//            ret.setField(fieldName);
//        } else {
//            ret.setName(StrUtil.isBlank(column.value()) ? StrUtil.toUnderlineCase(fieldName) : column.value());
//            ret.setField(fieldName);
////            ret.setLabel(column.comment());
////            ret.setComment(column.comment());
//            ret.setTypeHandler(column.typeHandler() == null ? "" : column.typeHandler().getName());
//        }
//
//        Col col = field.getAnnotation(Col.class);
//        if(col == null) {
//            if(ret.getType() == null) ret.setType(getColumnDefaultType(field));
//            if(ret.getLength() == null) ret.setLength(getColumnDefaultLength(field));
//        } else {
//            if(col.type() == null) {
//                if(ret.getType() == null) ret.setType(getColumnDefaultType(field));
//            } else {
//                ret.setType(col.type());
//            }
//            if(ret.getLength() == null) ret.setLength(col.length());
//        }
//
//        if(StrUtil.isBlank(ret.getLabel())) {
//            Schema schema = field.getAnnotation(Schema.class);
//            if(schema != null) {
//                ret.setLabel(schema.title());
//                if(StrUtil.isBlank(ret.getComment())) ret.setComment(schema.description());
//            }
//        }

        return ret;
    }

    private static Class<?> getFieldType(Field field) {
        if (Collection.class.isAssignableFrom(field.getType())) {
            Type genericType = field.getGenericType();
            if (genericType instanceof ParameterizedType pt) {
                return (Class<?>) pt.getActualTypeArguments()[0];
            }
            throw new RuntimeException("获取字段类型失败");
        }

        return field.getType();
    }

    private static ColumnType getColumnDefaultType(Field field) {
        Class<?> fieldType = getFieldType(field);
        if(fieldType.equals(Integer.class)) return ColumnType.INT;
        if(fieldType.equals(Long.class)) return ColumnType.BIGINT;
        if(fieldType.equals(Boolean.class)) return ColumnType.BIT;
        if(fieldType.equals(Double.class)) return ColumnType.DOUBLE;
        if(fieldType.equals(Float.class)) return ColumnType.FLOAT;
        if(fieldType.equals(BigDecimal.class)) return ColumnType.DECIMAL;
        if(fieldType.equals(Date.class)) return ColumnType.DATE;
        if(fieldType.equals(Time.class)) return ColumnType.DATETIME;
        if(fieldType.equals(Timestamp.class)) return ColumnType.TIMESTAMP;
        if(fieldType.equals(LocalDateTime.class)) return ColumnType.DATETIME;
        if(fieldType.equals(byte[].class)) return ColumnType.BLOB;
        if(fieldType.equals(YesNo.class)) return ColumnType.YESNO;
        if(fieldType.isEnum()) return ColumnType.ENUM;
        return ColumnType.VARCHAR;
    }

    private static int getColumnDefaultLength(Field field) {
        Class<?> fieldType = getFieldType(field);
        if(fieldType.equals(Integer.class)) return 10;
        if(fieldType.equals(Long.class)) return 20;
        if(fieldType.equals(Boolean.class)) return 1;
        if(fieldType.equals(Double.class)) return 0;
        if(fieldType.equals(Float.class)) return 0;
        if(fieldType.equals(BigDecimal.class)) return 0;
        if(fieldType.equals(Date.class)) return 0;
        if(fieldType.equals(Time.class)) return 0;
        if(fieldType.equals(Timestamp.class)) return 0;
        if(fieldType.equals(LocalDateTime.class)) return 0;
        if(fieldType.equals(byte[].class)) return 0;
        if(fieldType.isEnum()) return 8;
        return 64;
    }

    public static Class<?> getEntityClass(Class<?> controllerClass) {
        EntityClass entity = controllerClass.getAnnotation(EntityClass.class);
        if(entity != null) return entity.value();

//        return ReflectKit.getEntityClass(controllerClass);
        throw new RuntimeException(controllerClass.getName() + "没有标注实体类");
    }

    private static void checkColumnExist(Seed seed, String columnName) {
        if(StrUtil.isBlank(columnName)) throw new RuntimeException("列名不能为空");

//        Map<String, Column> columns = seed.getColumns();
//        columnName = StrUtil.toUnderlineCase(columnName);
//        Column column = columns.get(columnName);
//        if(column == null) {
//            throw new RuntimeException(String.format("【%s】下列【%s】不存在", seed.getName(), columnName));
//        }
    }
}
